﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNADota.Camera;
using XNADota.ContentPipeline.Data;
using XNADota.GamePlay;
using XNADota.MDX;

namespace XNADota.Graphics
{
    public class SceneManager
    {
        public const int MaxPaletteInstanceCount = 1024 * 16;
        public const int MaxSpriteInstanceCount = 256;
        public const int DefaultScreenWidth = 1024;
        public const int DefaultScreenHeight = 768;
        public const int MaxMovingObjectsCount = 2048;
        public const int MaxSeletableObjectCount = 2048;

        private Game _Game;
        private GameSettings _Settings;

        private PaletteRendererManager _PaletteRendererManager;
        private List<Graphics.Techniques.Material> _MaterialContainer;
        private List<Effect> _EffectContainer;

        // update list, 2D & 3D
        private int _ToUpdatePaletteInstancesArrayCount;
        private PaletteInstance[] _ToUpdatePaletteInstancesArray;

        // draw list, 2D
        private int _ToDrawSpriteInstanceArrayCount;
        private SpriteInstance[] _ToDrawSpriteInstanceArray;

        // draw list, 3D
        private int _ToDrawPaletteInstancesArrayCount;
        private PaletteInstance[] _ToDrawPaletteInstancesArray;

        // moving objects
        private int _MovingObjectsArrayCount;
        private PaletteInstance[] _MovingObjectsArray;

        // seletable objects
        private int _SelectableObjectsArrayCount;
        private PaletteInstance[] _SeletableObjectsArray;

        // Scene Manager Algorithm Core, 3D
        private OctTree _OctTree;

        // Camera
        private Matrix _View;
        private Matrix _Projection;

        public PaletteRendererManager PaletteRendererManager
        {
            get { return _PaletteRendererManager; }
        }

        // Interface
        private static SceneManager _Instance = new SceneManager();
        public static SceneManager Instance
        {
            get { return _Instance; }
        }

        private SceneManager()
        {
            _OctTree = new OctTree(new BoundingBox(new Vector3(-128 * 128, -128 * 128, -128 * 128), new Vector3(128 * 256, 128 * 256, 128 * 256)), 0);

            // draw list, 3D
            _ToDrawPaletteInstancesArrayCount = 0;
            _ToDrawPaletteInstancesArray = new PaletteInstance[MaxPaletteInstanceCount];

            // draw list, 2D
            _ToDrawSpriteInstanceArrayCount = 0;
            _ToDrawSpriteInstanceArray = new SpriteInstance[MaxSpriteInstanceCount];

            // update list, 2D & 3D
            _ToUpdatePaletteInstancesArrayCount = 0;
            _ToUpdatePaletteInstancesArray = new PaletteInstance[MaxPaletteInstanceCount];

            // moving objects
            _MovingObjectsArrayCount = 0;
            _MovingObjectsArray = new PaletteInstance[MaxMovingObjectsCount];

            // seletable ojbects
            _SelectableObjectsArrayCount = 0;
            _SeletableObjectsArray = new PaletteInstance[MaxSeletableObjectCount];

            // render effects and material
            _MaterialContainer = new List<Graphics.Techniques.Material>();
            _EffectContainer = new List<Effect>();
        }

        public void Initialize(Game game, GameSettings setting)
        {
            _Game = game;
            _Settings = setting;
            _PaletteRendererManager = new PaletteRendererManager(game);
        }

        public void AddInstance(PaletteInstance Instance)
        {
            // selectable objects
            if (Instance.IsSelectable)
            {
                _SeletableObjectsArray[_SelectableObjectsArrayCount] = Instance;
                _SelectableObjectsArrayCount++;
            }

            // Currently, it does not support dynamically detect moving objects, so save them in an array
            if (Instance.IsMovable)
            {
                _MovingObjectsArray[_MovingObjectsArrayCount] = Instance;
                _MovingObjectsArrayCount++;
            }
            else
            {
                if (Instance is SpriteInstance)
                {
                    throw new NotSupportedException("This operation is not supported.");
                }
                else
                {
                    _OctTree.InsertPaletteInstance(Instance);
                }
            }
        }

        public void Update(GameTime gameTime, BaseCamera Camera)
        {
            // Set Camera
            _View = Camera.View;
            _Projection = Camera.Projection;

            // update static objects
            _OctTree.Update(Camera.BoundingFrustum);

            // update moving objects;
            for (int i = 0; i < _MovingObjectsArrayCount; i++)
            {
                _MovingObjectsArray[i].Update(Camera.BoundingFrustum);
                if (_MovingObjectsArray[i].IsVisible)
                    AddIntoRenderQueue(_MovingObjectsArray[i]);
            }

            // Update Logics
            Update(gameTime);

        }

        #region update queue
        public PaletteInstance[] SelectedObjects()
        {
            List<PaletteInstance> list = new List<PaletteInstance>();

            for (int i = 0; i < _SelectableObjectsArrayCount; i++)
                if (_SeletableObjectsArray[i].IsSelected)
                    list.Add(_SeletableObjectsArray[i]);

            return list.ToArray();
        }

        public void DeselectAll()
        {
            for (int i = 0; i < _SelectableObjectsArrayCount; i++)
                _SeletableObjectsArray[i].SetSelected(false);
        }

        public PaletteInstance[] Select(Rectangle selectRectangle)
        {
            int length = 0;
            PaletteInstance[] ret = new PaletteInstance[128];
            Array.Clear(ret, 0, ret.Length);

            for (int i = 0; i < _SelectableObjectsArrayCount; i++)
            {
                PaletteInstance Instance = _SeletableObjectsArray[i];

                Vector3 screenPos = XNADota.Instance.GraphicsDevice.Viewport.Project(_SeletableObjectsArray[i].Position, _Projection, _View, Matrix.Identity);
                if (selectRectangle.Contains((int)screenPos.X, (int)screenPos.Y))
                {
                    ret[length] = Instance;
                    length++;
                }
            }

            return ret;
        }

        public PaletteInstance[] Select(Ray ray)
        {
            int length = 0;
            PaletteInstance[] ret = new PaletteInstance[128];
            Array.Clear(ret, 0, ret.Length);

            for (int i = 0; i < _SelectableObjectsArrayCount; i++)
            {
                PaletteInstance Instance = _SeletableObjectsArray[i];

                if (Instance.Select(ray))
                {
                    ret[length] = Instance;
                    length++;
                }
            }

            return ret;
        }

        public void Update(GameTime gameTime)
        {
            for (int i = 0; i < _ToUpdatePaletteInstancesArrayCount; i++)
                _ToUpdatePaletteInstancesArray[i].Update(gameTime);
        }

        public void AddUpdateInstance(PaletteInstance Instance)
        {
            _ToUpdatePaletteInstancesArray[_ToUpdatePaletteInstancesArrayCount] = Instance;
            _ToUpdatePaletteInstancesArrayCount++;
        }
        #endregion

        #region render queue
        private void ClearRenderQueue()
        {
            _ToDrawPaletteInstancesArrayCount = 0;
            _ToDrawSpriteInstanceArrayCount = 0;
        }

        public void AddIntoRenderQueue(PaletteInstance Instance)
        {
            if (Instance is SpriteInstance)
            {
                _ToDrawSpriteInstanceArray[_ToDrawSpriteInstanceArrayCount++] = Instance as SpriteInstance;
            }
            else
            {
                _ToDrawPaletteInstancesArray[_ToDrawPaletteInstancesArrayCount++] = Instance;
            }
        }

        public void Render()
        {
            // things are divided by renderers
            // 3D parts
            for (int i = 0; i < _ToDrawPaletteInstancesArrayCount; i++)
                _ToDrawPaletteInstancesArray[i].Draw(_View, _Projection);

            // selectable objects
            for (int i = 0; i < _SelectableObjectsArrayCount; i++)
            {
                PaletteInstance instance = _SeletableObjectsArray[i];
                if (instance.IsSelected)
                {
                    PaletteRendererManager manager = SceneManager.Instance.PaletteRendererManager;
                    ModelRenderer render = manager.GetPaletteRenderer(EPaletteRendererType.Doodad, "SelectionCircle") as ModelRenderer;
                    Vector3 position = GameplayWorld.Instance.PathManager.GetHeight(instance.Position);
                    Matrix world = instance.Orientation *
                                   Matrix.CreateScale(instance.SelectableCircleScale) *
                                   Matrix.CreateTranslation(position - new Vector3(0, 15, 0) * instance.SelectableCircleScale);
                    int toPlayIndex = render.Player.FindName("Friendly");
                    if (toPlayIndex != -1)
                    {
                        SequencePlayer player = new SequencePlayer(render.Player[toPlayIndex]);
                        player.Start(1.0f);
                    }
                    render.Draw(world, _View, _Projection);
                }
            }


            // 2D parts
            if (_ToDrawSpriteInstanceArrayCount > 0)
            {
                (_ToDrawSpriteInstanceArray[0].PaletteReference.PaletteRenderer as SpriteRenderer).Begin();
                for (int i = 0; i < _ToDrawSpriteInstanceArrayCount; i++)
                {
                    _ToDrawSpriteInstanceArray[i].Draw(_Settings.ScreenWidth, _Settings.ScreenHeight);
                }
                (_ToDrawSpriteInstanceArray[0].PaletteReference.PaletteRenderer as SpriteRenderer).End();
            }


            // Update Visible of each object
            ClearRenderQueue();
        }
        #endregion // render queue

        public Graphics.Techniques.Material GetMaterial(Graphics.Techniques.Material material)
        {
            Graphics.Techniques.Material reference =
                _MaterialContainer.Find(delegate(Graphics.Techniques.Material instance) { return material.Equals(instance); });
            if (reference == null)
            {
                reference = material;
                _MaterialContainer.Add(material);
            }

            return reference;
        }

        public Effect GetEffect(Effect effect)
        {
            Effect reference =
                _EffectContainer.Find(delegate(Effect instance) { return effect.GetType() == instance.GetType(); });
            if (reference == null)
            {
                reference = effect;
                _EffectContainer.Add(effect);
            }

            return reference;
        }

        // this method will be no more useful, to be reverted.
        public void AddRenderUnit(RenderUnit unit, Effect effect, Graphics.Techniques.Material material, Graphics.Techniques.Layer layer)
        {
            effect.Begin();

            material.ApplyEffect(effect as BasicEffect);

            (effect as BasicEffect).Texture = layer.Texture;
            (effect as BasicEffect).TextureEnabled = layer.EnableTexture;
            RenderStateManager.Instance.SetRenderStates(layer.RenderStates);

            (effect as BasicEffect).World = unit.World;
            effect.GraphicsDevice.VertexDeclaration = unit.VertexDeclaration;
            effect.GraphicsDevice.Vertices[0].SetSource(unit.DynamicVertexBuffer, 0,
                                                 VertexPositionNormalTexture.SizeInBytes);
            effect.GraphicsDevice.Indices = unit.IndexBuffer;

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();

                // Draw the geometry.
                effect.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                                             0, 0, unit.VerticesCount,
                                             0, unit.TriangleCount);

                pass.End();
            }

            effect.End();
        }
    }
}
